﻿using System;
using System.Linq;
using System.Collections.Generic;
using System.Collections;
using System.Text;
using System.IO;
using System.Reflection;
using System.Xml.Serialization;
using System.Xml.Schema;
using Interfaces;

namespace PozrSvc
{
    public class PositionHandler : IPositionHandler
    {
        /// <summary>
        /// Data class for position originator plugin.
        /// Holds and instance of the loaded plugin, as well as the Plugin's assembly path
        /// </summary>
        public class OriginatorModule
        {
            //This is the actual Plugin object.. 
            //Holds an instance of the plugin to access
            //ALso holds assembly path... not really necessary
            private IPositionOriginator myInstance = null;
            private string myAssemblyPath = "";
            private bool validPlugin;

            public bool IsValid
            {
                get { return validPlugin; }
            }

            public OriginatorModule()
            {
                validPlugin = true;
            }

            public OriginatorModule(bool valid)
            {
                validPlugin = valid;
            }

            public IPositionOriginator Instance
            {
                get { return myInstance; }
                set { myInstance = value; }
            }
            public string AssemblyPath
            {
                get { return myAssemblyPath; }
                set { myAssemblyPath = value; }
            }
        }

        private Dictionary<PositionOriginator, OriginatorModule> modules = new Dictionary<PositionOriginator, OriginatorModule>();
        private int centerMarkerId = -1;

        static System.Text.ASCIIEncoding ByteEncoding = new System.Text.ASCIIEncoding();
        private int cacheInterval = 10;

        public void WriteDebugLog(string s)
        {
            Middleware.OnLogEvent(s);
        }

        public string WorkingDirectory
        {
            get { return Middleware.ApplicationDirectory; }
        }

        protected static class AbsolutePositionCache
        {
            static IAbsoluteData cache;
            static DateTime time;

            public static float Latitude
            {
                get { return Convert.ToSingle(cache.Latitude); }
            }

            public static float Longitude
            {
                get { return Convert.ToSingle(cache.Longitude); }
            }

            public static void Update(IAbsoluteData data)
            {
                cache = data;
                time = DateTime.Now;
            }

            public static IAbsoluteData GetData(int validityInterval)
            {
                if (time.Add(TimeSpan.FromSeconds(validityInterval)) > DateTime.Now)
                {
                    return cache;
                }
                return null;
            }
        }

        public bool SetRadioState(PositionOriginator type, bool state)
        {
              if (modules.ContainsKey(type))
              {
                  if (state)
                  {
                      modules[type].Instance.SetRadioOn();
                      Middleware.WriteLogToFile(modules[type].Instance.Name + " module turned on.");
                  }
                  else
                  {
                      modules[type].Instance.SetRadioOff();
                      Middleware.WriteLogToFile(modules[type].Instance.Name + " module turned off.");
                  }
                  return true;
              }
            return false;
        }

        public bool GetRadioState(PositionOriginator type)
        {
            if(modules.ContainsKey(type))
            {
                return modules[type].Instance.GetRadioStatus();
            }
            else
            {
                return false;
            }
        }

        public bool RequestData(PositionOriginator positionOriginator)
        {
            if (modules.ContainsKey(positionOriginator))
            {
                modules[positionOriginator].Instance.DataRequest();
                return true;
            }

            Middleware.WriteLogToFile("Invalid request!");
            return false;
        }

        public PositionHandler()
        {
            Middleware.Settings.InitSettingsGroup("PositionHandler");
            Middleware.Settings.InitSettingsGroup("Autostart");

            try
            {
                object temp = Middleware.Settings.GetSetting("PositionHandler", "GpsCacheInterval");
                if (temp == null)
                {
                    Middleware.Settings.SetSetting("PositionHandler", "GpsCacheInterval", 15); // default value
                }
                else
                {
                    cacheInterval = int.Parse(temp.ToString());
                }
            }
            catch (Exception e)
            {
                Middleware.WriteLogToFile(e.Message);
            }
            Middleware.NewSignalEvent += this.NewSignalEventHandler;
            Middleware.BestSignalEvent += this.BestSignalEventHandler;
        }

        public void LoadModules()
        {
            modules.Clear();
            // DEBUG : REMOVE : TODO : STB
            Dictionary<string, object> temp = new Dictionary<string, object>();
            temp.Add("GPS", @"\Program Files\Pozr\GPS.dll");
            temp.Add("Cell", @"\Program Files\Pozr\Cell.dll");

            try
            {
                foreach (KeyValuePair<string, object> kp in /*Middleware.Settings.GetSettings("AutoStart")*/ temp)
                {
                    FileInfo file = new FileInfo(kp.Value.ToString());
                    if (file.Exists)
                    {
                        //Add the module
                        OriginatorModule module = this.AddPositionOriginator(file.FullName);

                        if (module.IsValid)
                        {
                            module.Instance.RequestedDataAvailable += new RequestedDataAvailableHandler(this.RequestedDataAvailableHandler);
                            module.Instance.RadioSwitchedOnEvent += new RadioSwitchedOnEventHandler(this.RadioSwitchedOnEventHandler);
                            module.Instance.RadioSwitchedOffEvent += new RadioSwitchedOffEventHandler(this.RadioSwitchedOffEventHandler);
                            module.Instance.NewSignalEvent += new NewSignalEventHandler(this.NewSignalEventHandler);
                            module.Instance.BestSignalEvent += new BestSignalEventHandler(this.BestSignalEventHandler);

                            modules.Add(module.Instance.GetType(), module);
                            module.Instance.SetRadioOn();
                            System.Diagnostics.Debug.WriteLine(module.Instance.Name + " module loaded.");
                        }
                        else
                        {
                            System.Diagnostics.Debug.WriteLine(file.FullName + " module is invalid.");
                        }
                    }
                }

            }
            catch (Exception e)
            {
                System.Diagnostics.Debug.WriteLine(e.Message);
            }
        }

        private OriginatorModule AddPositionOriginator(string FileName)
        {
            //Create a new assembly from the plugin file we're adding..
            Assembly pluginAssembly = Assembly.LoadFrom(FileName);

            //Next we'll loop through all the Types found in the assembly
            foreach (Type pluginType in pluginAssembly.GetTypes())
            {
                if (pluginType.IsPublic) //Only look at public types
                {
                    if (!pluginType.IsAbstract)  //Only look at non-abstract types
                    {
                        Type typeInterface = null;

                        //Gets a type object of the interface we need the plugins to match
                        foreach (Type t in pluginType.GetInterfaces())
                        {
                            if (t.FullName == "Interfaces.IPositionOriginator")
                                typeInterface = t;
                        }

                        //Make sure the interface we want to use actually exists
                        if (typeInterface != null)
                        {
                            //Create a new plugin since the type implements the IPositionOriginator interface
                            OriginatorModule newPlugin = new OriginatorModule();

                            //Set the filename where we found it
                            newPlugin.AssemblyPath = FileName;

                            //Create a new instance and store the instance in the collection for later use
                            newPlugin.Instance = (IPositionOriginator)Activator.CreateInstance(pluginAssembly.GetType(pluginType.ToString()));

                            //Set the Plugin's host to this class which inherited IPluginMiddleware
                            newPlugin.Instance.Host = this;

                            //Call the initialization sub of the plugin
                            newPlugin.Instance.Initialize();

                            return (newPlugin);
                        }

                        typeInterface = null;
                    }
                }
            }
            pluginAssembly = null;

            return new OriginatorModule(false);
        }

        /// <summary>
        /// Assigns a weak (new) proximity signal to an absolute position (approx. edge of the cell)
        /// </summary>
        /// <param name="proximityData">Proximity signal data</param>
        private bool AssignNewSignal(IProximityData proximityData)
        {
            IAbsoluteData abs_data = AbsolutePositionCache.GetData(cacheInterval);
            if (abs_data != null)
            {
                System.Diagnostics.Debug.WriteLine("Assigned signal " + proximityData.Identifier + " to " + abs_data.Identifier);
                return Middleware.Database.UpdateBorder(proximityData.Identifier, abs_data.Latitude, abs_data.Longitude, (short)proximityData.RelativeSignalStrength);
            }
            else
            {
                return false;
            }
        }

        /// <summary>
        /// Assigns a strong (best) proximity signal to an absolute position (approx. center of the cell)
        /// </summary>
        /// <param name="proximityData">Proximity signal data</param>
        private bool AssignBestSignal(IProximityData proximityData)
        {
            IAbsoluteData abs_data = AbsolutePositionCache.GetData(cacheInterval);
            if (abs_data != null)
            {
                // return pairedTable.UpdateCenter(proximityData, AbsolutePositionCache.GetGpsData(cacheInterval));
                return Middleware.Database.UpdateCenter(proximityData.Identifier, abs_data.Latitude, abs_data.Longitude, (short)proximityData.RelativeSignalStrength);
            }
            else
            {
                return false;
            }
        }

        private void NewSignalEventHandler(object sender, PositionEventArgs e)
        {
            foreach (IPositionData data in e.Position)
            {
                System.Diagnostics.Debug.WriteLine("NewSignalEventHandler: " + data.Identifier);
                if (data is CellData) Middleware.OnNewCellDataAvailable(this, new PositionEventArgs(data, PositionEventArgs.Operation.New));

                if (data is IAbsoluteData)
                {
                    NewAbsoluteSignalEventHandler(new PositionEventArgs(data, PositionEventArgs.Operation.New));
                }
                else if (data is IProximityData)
                {
                    NewRelativeSignalEventHandler(data as IProximityData);
                }
                else
                {
                    throw new InvalidDataException("Invalid position data");
                }
            }
        }

        private void BestSignalEventHandler(object sender, PositionEventArgs e)
        {
            foreach (IPositionData data in e.Position)
            {
                System.Diagnostics.Debug.WriteLine("Best signal: " + data.Identifier);
                if (data is IAbsoluteData)
                {
                    BestAbsoluteSignalEventHandler(data as IAbsoluteData);
                }
                else if (data is IProximityData)
                {
                    BestRelativeSignalEventHandler(data as IProximityData);
                }
                else
                {
                    throw new InvalidDataException("Invalid position data");
                }
            }
        }

        private void NewRelativeSignalEventHandler(IProximityData data)
        {
            if (AssignNewSignal(data))
            {
                Middleware.PluginServices.OnPairedDataAvailable(this, AbsolutePositionCache.GetData(2 * cacheInterval), data);

                PozrService.Measurement m = new PozrService.Measurement();
                m.hash = data.Identifier;
                m.m_type = false;
                m.s_type = (byte)data.Originator;
                m.lat = AbsolutePositionCache.Latitude;
                m.lon = AbsolutePositionCache.Longitude;
                m.strength = (short)data.RelativeSignalStrength;
            }
        }

        private void BestRelativeSignalEventHandler(IProximityData data)
        {
            if (AssignBestSignal(data))
            {
                Middleware.PluginServices.OnPairedDataAvailable(this, AbsolutePositionCache.GetData(2 * cacheInterval), data);

                PozrService.Measurement m = new PozrService.Measurement();
                m.hash = data.Identifier;
                m.m_type = true;
                m.s_type = (byte)data.Originator;
                m.lat = AbsolutePositionCache.Latitude;
                m.lon = AbsolutePositionCache.Longitude;
                m.strength = (short)data.RelativeSignalStrength;

                if (centerMarkerId < 0)
                {
                    centerMarkerId = Middleware.Markers.AddProto(System.Reflection.Assembly.GetExecutingAssembly().GetManifestResourceStream("Pozr.Resources.point.png"),
                                             System.Drawing.Point.Empty,
                                             System.Drawing.Point.Empty,
                                             this.GetType().ToString());
                }
                Middleware.Markers.Add(m.lat, m.lon, centerMarkerId, "C", null);
            }
        }

        private void NewAbsoluteSignalEventHandler(PositionEventArgs args)
        {
            foreach (IAbsoluteData data in args.Position)
            {
                if (Double.IsNaN(data.Latitude) || Double.IsNaN(data.Longitude))
                {
                    // throw GpsSignalLost event
                    Middleware.OnNewAbsoluteDataAvailable(this, args);
                }
                else
                {
                    AbsolutePositionCache.Update(data);
                    Middleware.OnNewAbsoluteDataAvailable(this, args);
                }
            }
        }

        private void BestAbsoluteSignalEventHandler(IAbsoluteData data)
        {
            // TODO
        }

        private void RequestedDataAvailableHandler(object sender, PositionEventArgs e)
        {
            switch (((IPositionOriginator)sender).GetType())
            {
                case PositionOriginator.Cell:
                    if (e.Position == null)
                    {
                        Middleware.WriteLogToFile("Requested Cell data: Unavailable!");
                    }
                    else
                    {
                        NewSignalEventHandler(this, e);
                        Middleware.WriteLogToFile("Requested Cell data:\r\n ID: " + ((CellData)e.Position[0]).Id.ToString() + " MNC: " + ((CellData)e.Position[0]).Mnc.ToString() + " Signal: " + ((CellData)e.Position[0]).SignalStrength.ToString());
                    }
                    break;
                case PositionOriginator.Gps:
                    if (e.Position == null)
                    {
                        Middleware.WriteLogToFile("Requested GPS data: Unavailable!");
                    }
                    else
                    {
                        Middleware.WriteLogToFile("Requested GPS data:\r\n Lat: " + ((GPSData)e.Position[0]).Latitude.ToString() + "\r\n Lon: " + ((GPSData)e.Position[0]).Longitude.ToString());
                    }
                    break;
                case PositionOriginator.Wifi:
                    string temp = "Requested Wifi data:\r\n";
                    if (e.Position == null)
                    {
                        temp += "Unavailable!";
                    }
                    else
                    {
                        foreach (WifiData wifiData in e.Position)
                        {
                            temp += wifiData.Name + " | " + BitConverter.ToString(wifiData.MacAddress) + " | " + wifiData.Rssi.ToString() + "\r\n";
                        }
                        NewSignalEventHandler(this, e);
                    }
                    Middleware.WriteLogToFile(temp);
                    break;
                default:
                    Middleware.WriteLogToFile("Invalid originator!");
                    break;
            }
        }

        private void RadioSwitchedOffEventHandler(object sender, EventArgs e)
        {
            Middleware.OnSignalChanged(sender as IPositionOriginator, false);
            
        }

        private void RadioSwitchedOnEventHandler(object sender, EventArgs e)
        {
            Middleware.OnSignalChanged(sender as IPositionOriginator, true);
        }

        public string[] GetCurrentContextHashes(PositionOriginator[] originators)
        {
            List<string> hashes = new List<string>();
            /*
                foreach (KeyValuePair<PositionOriginator, OriginatorModule> module in modules)
                {
                    if (module.Value.Instance is IProximityOriginator && originators.Contains(module.Key) && module.Value.Instance.GetRadioStatus())
                    {
                        hashes.AddRange((module.Value.Instance as IProximityOriginator).GetCurrentContextHashes());
                    }
                }
            */
            return hashes.ToArray();
        }

        #region ICommonInterface Members

        public bool InitSettingsGroup(string app_id)
        {
            return Middleware.Settings.InitSettingsGroup(app_id);
        }

        public bool DeleteSettingsGroup(string app_id)
        {
            return Middleware.Settings.DeleteSettingsGroup(app_id);
        }

        public bool SettingsGroupAvailable(string app_id)
        {
            return Middleware.Settings.SettingsGroupAvailable(app_id);
        }

        public bool SetSetting(string app_id, string setting_name, object data)
        {
            return Middleware.Settings.SetSetting(app_id, setting_name, data);
        }

        public bool DeleteSetting(string app_id, string setting_name)
        {
            return Middleware.Settings.DeleteSetting(app_id, setting_name);
        }

        public object GetSetting(string app_id, string setting_name)
        {
            return Middleware.Settings.GetSetting(app_id, setting_name);
        }

        #endregion

    }
}
